const element = document.documentElement

let isLinsteningMouse = false

const contexts = new Map()

element.addEventListener('mousedown', event => {
  const context = Object.create(null)
  contexts.set('mouse' + (1 << event.button), context)
  start(event, context)

  const mousemove = event => {
    let button = 1
    while(button <= event.buttons) {
      if (button & event.buttons) {
        // order of buttons & button property is not same
        let key = null
        if (button === 2) {
          key = 4
        } else if (button === 4) {
          key = 2
        } else {
          key = button
        }
        const context = contexts.get('mouse' + key)
        move(event, context)
      }
      button = button << 1
    }
  }
  const mouseup = event => {
    const context = contexts.get('mouse' + (1 << event.button))
    end(event, context)
    contexts.delete('mouse' + (1 << event.button))

    if (event.buttons === 0) {
      element.removeEventListener('mousemove', mousemove)
      element.removeEventListener('mouseup', mouseup)
      isLinsteningMouse = false
    }
  }
  if (!isLinsteningMouse) {
    element.addEventListener('mousemove', mousemove)
    element.addEventListener('mouseup', mouseup)
    isLinsteningMouse = true
  }
})

element.addEventListener('touchstart', event => {
  for (let touch of event.changedTouches) {
    const context = Object.create(null)
    contexts.set(touch.identifier, context)
    start(touch, context)
  }
})
element.addEventListener('touchmove', event => {
  for (let touch of event.changedTouches) {
    const context = contexts.get(touch.identifier)
    move(touch, context)
  }
})
element.addEventListener('touchend', event => {
  for (let touch of event.changedTouches) {
    const context = contexts.get(touch.identifier)
    end(touch, context)
    contexts.delete(touch.identifier)
  }
})
element.addEventListener('touchcancel', event => {
  for (let touch of event.changedTouches) {
    const context = contexts.get(touch.identifier)
    cancel(touch, context)
    contexts.delete(touch.identifier)
  }
})

const start = ({clientX, clientY}, context) => {
  context.startX = clientX
  context.startY = clientY
  context.isTap = true
  context.isPan = false
  context.isPress = false

  context.handler = setTimeout(() => {
    context.isTap = false
    context.isPan = false
    context.isPress = true
    context.handler = null
    console.log('press')
  }, 500)
}
const move = ({clientX, clientY}, context) => {
  const dx = clientX - context.startX
  const dy = clientY - context.startY
  if (!context.isPan && dx ** 2 + dy ** 2 > 100) {
    context.isTap = false
    context.isPan = true
    context.isPress = false
    clearTimeout(context.handler)
    console.log('panstart')
  }
  if (context.isPan) {
    console.log(dx, dy)
    console.log('pan')
  }
}
const end = ({clientX, clientY}, context) => {
  if (context.isTap) {
    console.log('tap')
    clearTimeout(context.handler)
  }
  if (context.isPan) {
    console.log('panend')
  }
  if (context.isPress) {
    console.log('pressend')
  }
}
const cancel = ({clientX, clientY}, context) => {
  clearTimeout(context.handler)
}